/*============================================================================
  File:      Customize.cpp

  Summary:   Completed implementation for selected functions of the plug-in
			 algorithm that implements the pair-wise linear regression model. 
			 You should copy these functions over the shell-project 
			 implementations while going through the "Customization" section 
			 of the tutorial.

  Date:		 September 12, 2003
------------------------------------------------------------------------------
  This file is part of the Microsoft SQL Server Code Samples.
 
  Copyright (C) 2003 Microsoft Corporation.  All rights reserved.
 
This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
============================================================================*/


/*****************************

	Customization Step 3, Item 3

 *****************************/

STDMETHODIMP ALGORITHM::Predict( 
    /* [in] */ IDMContextServices*	in_pContext,
    /* [in] */ DM_PREDICTION_FLAGS	in_Flags,
    /* [in] */ IDMAttributeGroup*	in_pPredAttGroup,
    /* [in] */ DM_CaseID			in_CaseID,
    /* [in] */ ULONG				in_ulCaseValues,
    /* [in] */ DM_ATTRIBUTE_VALUE*	in_rgValues,
    /* [in] */ ULONG				in_ulMaxPredictions,
    /* [in] */ ULONG				in_ulMaxStates,
    /* [out] */ DM_ATTRIBUTE_STAT**	io_prgPredictions,
    /* [out] */ ULONG*				out_pulPredictions)
{
	// This is the function used to extract predictions from your model. 
	//
	// The output variables (i.e., the variables for which you are being requested
	// to return predictions) are specified in a "group" using IDMAttributeGroup; you 
	// iterate through this group using IDMAttributeGroup::Reset() and 
	// IDMAttributeGroup::Next(). Some of the attributes in this group, however, may
	// not be allowed to be predicted, based on the input DM_PREDICTION_FLAGS in_flags.
	//
	// The known input variables are supplied in a "case" of attribute/value pairs
	// in_rgValues (this array has in_ulCaseValues elements). 
	//
	// ************* IMPORTANT NOTE: 
	// If in_rgValues[i].Attribute is equal to DM_UNSPECIFIED, you are to ignore it in the 
	// input case!!!
	//
	// The output distributions are to be placed in io_prgPredictions: if the contents of
	// this pointer is NULL, you need to allocate the appropriate amount of space for it 
	// using the in_pContext; otherwise, if io_prgPredictions is not NULL, you are guaranteed by
	// the caller that out_pulPredictions has enough space to hold all of the necessary
	// predictions.
	//
	// You should limit the output list to at most in_ulMaxPredictions, where each
	// discrete-variable prediction contains the description for at most 
	// in_ulMaxStates states.
	// 
	// The supplied DM_PREDICTION_FLAGS determine the behaviour of the call:
	//
	//	DMPF_EXCLUDE_INPUT_ATTRIBUTES			Don't predict any attribute that is explicitly
	//											specified in in_rgValues
	//
	//	DMPF_ONLY_INPUT_ATTRIBUTES				Only predict those values that are explicitly
	//											specified in in_rgValues
	//
	//	DMPF_EXCLUDE_MISSING_STATE				Don't return a prediction about the missing
	//											state
	//
	//	DMPF_ATTRIBUTE_SORT_ASCENDING			Sort io_prgPredictions in ascending order, based
	//											on the value of (in_flags | DMPF_ATT_SORT_MASK)
	//
	//	DMPF_STATE_SORT_ASCENDING				Sort the states of each prediction in ascending
	//											order, based on the value of 
	//											(in_flags | DMPF_STATE_SORT_MASK)
	//
	//	DMPF_INCLUDE_STATS						Populate the rgStateStats member for each 
	//											distribution. Similar to the io_prgPredictions passed in,
	//											if rgStateStats is NULL, you need to allocate the
	//											necessary memory; otherwise, you are guaranteed by
	//											the caller that rgStateStats contains enough space
	//											for your target distribution.
	//
	//	DMPF_INCLUDE_NODE_ID					Populate DM_ATTRIBUTE_STAT::strNodeID for each
	//											prediction. This ID is supposed to be "relevant"
	//											to the prediction that was made, and may be
	//											distinct from the "drill-through" id and/or the
	//											"mining model dimension" id. (See GetNodeIDsForCase).
	//
	//	DMPF_INCLUDE_TIME						Not used for this function.
	//
	//	DMPF_ATT_SORT_MASK						Mask for determining how to sort attributes
	//
	//	DMPF_ATT_SORT_EXIST_SUPPORT				Sort attributes by support (i.e. number
	//											of cases in the training data)
	//
	//	DMPF_ATT_SORT_EXIST_PROB				Sort attributes by posterior probability of
	//											non-missing.
	//
	//	DMPF_ATT_SORT_EXIST_ADJUST_PROB			Sort attributes by "adjusted probability". The
	//											adjusted probability is used to implement (e.g.)
	//											a penalty for popular items.
	//
	//	DMPF_STATE_SORT_MASK					Mask for determining how to sort discrete states
	//
	//	DMPF_STATE_SORT_PROB					Sort states by posterior probability
	//
	//	DMPF_STATE_SORT_SUPPORT					Sort states by support
	//
	//	DMPF_STATE_SORT_VARIANCE				Sort states by posterior variance
	//
	//	DMPF_STATE_SORT_ADJUST_PROB				Sort states by "adjusted probability"
	//
	//
	// A note on representing the posterior of a continuous distribution
	// -----------------------------------------------------------------
	// All continuous distributions are necessarily "binary continuous": there's a probability of 
	// missing vs. present, and then a conditional (continuous) distribution given that the attribute 
	// is present. The continuous posterior must is characterized (for the purposes of Predict) by
	// a mean and variance.
	//
	// Assuming the caller has requested Predict() to include statistics, the (DM_STATE_STAT*) 
	// rgStateStats member of each DM_ATTRIBUTE_STAT must be allocated to size = 2; one element 
	// specifies the distribution of the missing state, and the other element specifies both 
	// p(present) and the conditional distribution given that the attribute is present.
	// 
	// Suppose we have the following posterior distribution:
	//
	//
	// p(x)    = {    p(x is missing)                             if x is missing
    //				  p(x not missing) * p(x | x not missing)     if x is not missing
	//		     }
	//
	// and we want to populate a DM_ATTRIBUTE_STAT called dmattributestat. First, we would 
	// allocate two members for dmattributestat.rgStateStats. Then,
	//
	// // Specify p(x is not missing) and p(x | x is not missing) using a DM_STATE_STAT: 
	// // the posterior mean is specified using the Value member in this case
	// dmattributestat.rgStateStats[1].Value = DmstatevalueContinuous(/* posterior mean (given not missing)*/); 
	// dmattributestat.rgStateStats[1].dblVariance = // posterior variance (given not missing)
	// dmattributestat.rgStateStats[1].dblProbability = // p(x is not missing)
	// dmattributestat.rgStateStats[1].dblSupport =		// Number cases where x is not missing
	// dmattributestat.rgStateStats[1].dblProbabilityVariance = 0; // Variance of p(x is not missing). Not used.
	//
	// // Specify p(x is missing): put missing as the Value, and specify the probability of missing.
	// dmattributestat.rgStateStats[0].Value = DmstatevalueContinuous(dblMissing); 
	// dmattributestat.rgStateStats[0].dblProbability = // p(x is missing)
	// dmattributestat.rgStateStats[0].dblSupport =		// Number cases where x is missing
	// dmattributestat.rgStateStats[0].dblVariance = 0; // N/A: just set to 0
	// dmattributestat.rgStateStats[0].dblProbabilityVariance = 0; // Variance of p(x is missing). Not used.
	//

    
	// We need a dense representation of the case to pass into _lrpmodel.ExtractPosterior()
	// Also, we'll set up a vector of bools to indicate whether or not each attribute
	// has been specified as an input or not. 

	VDBL	vdblCaseDense(*this);
	VB		vbInput(*this);

	ULONG cAttribute = _lrpmodel._cAttribute;

	vdblCaseDense.resize(cAttribute, dblMissing);
	vbInput.resize(cAttribute, false);

	// AVPair = attribute/value pair
	for (UINT iAVPair = 0; iAVPair < in_ulCaseValues; iAVPair++)
	{
		DM_ATTRIBUTE_VALUE& dmattributevalue = in_rgValues[iAVPair];
		ULONG iAttribute = dmattributevalue.Attribute;

		// Check if the attribute is "unspecified" and if so, ignore.
		if (iAttribute == DM_UNSPECIFIED)
		{
			continue;
		}
			
		// Note that the translation of the values MUST match that which
		// is done in the CASEPROCESSOR in InsertCases(). Because we 
		// only have continuous variables, this is a non-issue for us, but 
		// you need to pay attention to this for categorical variables.
		// See DataValues.h for details.
		DBL	 dblValue	= ::DblContinuous(dmattributevalue.Value);
		vdblCaseDense[iAttribute]	= dblValue;
		vbInput[iAttribute]			= true;
	}
	
	// Check if we need to resize io_prgPredictions. If so, cycle over the attributes to
	// count.

	CComPtr<IDMMemoryAllocator>	spidmmemoryallocator	= NULL;
	CComPtr<IDMStringHandler>	spidmstringhandler		= NULL;

	if (io_prgPredictions == NULL)
	{
		ATLASSERT(false);
		return E_POINTER;
	}

	if (*io_prgPredictions == NULL)
	{
		// Need to allocate memory
		HRESULT hr	=	in_pContext->GetVectorAlloc(&spidmmemoryallocator);
		RETURN_ON_FAIL(hr);

		// Count the number of outputs
		ULONG	iAttributeOutput	= 0;
		UINT	cAttributePredict	= 0;
		while ((hr = in_pPredAttGroup->Next(&iAttributeOutput)) != S_FALSE)
		{
			RETURN_ON_FAIL(hr);

			if ((in_Flags & DMPF_EXCLUDE_INPUT_ATTRIBUTES) && vbInput[iAttributeOutput])
			{
				continue;
			}
			else if ((in_Flags & DMPF_ONLY_INPUT_ATTRIBUTES) && !vbInput[iAttributeOutput])
			{
				continue;
			}

			cAttributePredict++;
		}

		if (cAttributePredict > in_ulMaxPredictions)
		{
			cAttributePredict = in_ulMaxPredictions;
		}

		hr	=	spidmmemoryallocator->Alloc(cAttributePredict*sizeof(DM_ATTRIBUTE_STAT), (byte**) io_prgPredictions);
		
		RETURN_ON_FAIL(hr);

		for (ULONG iAttribute = 0; iAttribute < cAttributePredict; iAttribute++)
		{
			// Make sure that rgStateStats is NULL so that we properly allocate

			DM_ATTRIBUTE_STAT& dmattributestat = *io_prgPredictions[iAttribute];

			dmattributestat.Attribute							= -1;
			dmattributestat.strNodeID							= (DMStringPtr) NULL;
			dmattributestat.dblMin								= 0.0;
			dmattributestat.dblMax								= 0.0;
			dmattributestat.dblExistingSupport					= 0.0;
			dmattributestat.dblExistingProbability				= 0.0;
			dmattributestat.dblExistingAdjustedProbability		= 0.0;
			dmattributestat.ulStates							= 0;
			dmattributestat.rgStateStats						= NULL;
		}
	}
	
	// Cycle over the set of variables (possibly for the second time) and build the
	// predictions.

	HRESULT hr = in_pPredAttGroup->Reset();
	RETURN_ON_FAIL(hr);
	ULONG	iAttributeOutput	= 0;
	UINT	iPrediction			= 0;

	while ((hr = in_pPredAttGroup->Next(&iAttributeOutput)) != S_FALSE)
	{
		RETURN_ON_FAIL(hr);

		// Check for exclusions using DM_PREDICTION_FLAGS
		if ((in_Flags & DMPF_EXCLUDE_INPUT_ATTRIBUTES) && vbInput[iAttributeOutput])
		{
			continue;
		}
		else if ((in_Flags & DMPF_ONLY_INPUT_ATTRIBUTES) && !vbInput[iAttributeOutput])
		{
			continue;
		}

		// Put together the prediction
		DM_ATTRIBUTE_STAT& dmattributestat = *io_prgPredictions[iPrediction++];
		dmattributestat.Attribute = iAttributeOutput;
		if (in_Flags & DMPF_INCLUDE_NODE_ID)
		{
			// The caller is requesting a node id that corresponds to the
			// prediction. We'll simply use: "PredictionIDX", where X is the
			// attribute number. 
			
			if (spidmstringhandler == NULL)
			{
				hr = in_pContext->GetStringHandler(&spidmstringhandler);

				RETURN_ON_FAIL(hr);
			}
			
			WCHAR szUniqueName[200];
			swprintf(szUniqueName, L"PredictionID%d", iAttributeOutput);
			UINT cch = (UINT) wcslen(szUniqueName);
			
			hr = spidmstringhandler->CreateNewStringHandle(&dmattributestat.strNodeID);
			RETURN_ON_FAIL(hr);
			
			hr = spidmstringhandler->CopyBufferToHandle(dmattributestat.strNodeID,
														szUniqueName,
														cch);

			RETURN_ON_FAIL(hr);
		}

		// Min and max values
		dmattributestat.dblMin = _lrpmodel._vdblMin[iAttributeOutput];
		dmattributestat.dblMax = _lrpmodel._vdblMax[iAttributeOutput];

		// No missing values
		dmattributestat.dblExistingSupport		= _lrpmodel._cCase;
		dmattributestat.dblExistingProbability	= 1.0;
    
		// Not implementing an adjusted probability
		dmattributestat.dblExistingAdjustedProbability = 1.0;

		if (in_Flags & DMPF_INCLUDE_STATS)
		{
			// As described above, we have two states: one for missing, which
			// has probability zero in our case, and one for present, which will
			// also describe our continuous distribution.
			dmattributestat.ulStates = 2;
			if (in_ulMaxStates < 2)
			{
				if (in_ulMaxStates == 1)
				{
					dmattributestat.ulStates = 1;
				}
				else
				{
					return E_FAIL;
				}
			}

			
			if (dmattributestat.rgStateStats == NULL)
			{
				// Allocate the space
						if (spidmmemoryallocator == NULL)
				{
					hr	=	in_pContext->GetVectorAlloc(&spidmmemoryallocator);
						RETURN_ON_FAIL(hr);
				}

				hr	=	spidmmemoryallocator->Alloc(dmattributestat.ulStates*sizeof(DM_STATE_STAT), (byte**) &dmattributestat.rgStateStats);
				RETURN_ON_FAIL(hr);
			}
			memset( dmattributestat.rgStateStats, 0, dmattributestat.ulStates*sizeof(DM_STATE_STAT));

			// We know that regardless of the sorting criterion, the present state will always have 
			// higher "score" than the missing state. Thus:
			INT iStateStatPresent = 0;
			INT iStateStatMissing = 1;
			if (in_Flags & DMPF_STATE_SORT_ASCENDING)
			{
				// Caller wants lower-probability states first
				iStateStatPresent = 1;
				iStateStatMissing = 0;
			}

			// Get the posterior
			DBL dblPostMean = 0;
			DBL dblPostSD	= 0;
			hr = _lrpmodel.ExtractPosterior(vdblCaseDense, iAttributeOutput, dblPostMean, dblPostSD);
			RETURN_ON_FAIL(hr);
			if (iStateStatPresent < (INT) dmattributestat.ulStates)
			{
				// p(x)		: Use the posterior mean for the value 
				dmattributestat.rgStateStats[iStateStatPresent].Value		= ::DmstatevalueContinuous(dblPostMean); 
				dmattributestat.rgStateStats[iStateStatPresent].dblVariance = sqrt(dblPostSD);

				// p(present) = 1
				dmattributestat.rgStateStats[iStateStatPresent].dblProbability			= 1.0;
				dmattributestat.rgStateStats[iStateStatPresent].dblSupport				= _lrpmodel._cCase;
				dmattributestat.rgStateStats[iStateStatPresent].dblProbabilityVariance	= 0;
			}

			if (iStateStatMissing < (INT) dmattributestat.ulStates)
			{
				// p(missing) = 0
				dmattributestat.rgStateStats[iStateStatMissing].Value = DmstatevalueCategorical(dblMissing); 
				dmattributestat.rgStateStats[iStateStatMissing].dblProbability = 0.0;
				dmattributestat.rgStateStats[iStateStatMissing].dblSupport = 0.0;
				dmattributestat.rgStateStats[iStateStatMissing].dblVariance = 0; 
				dmattributestat.rgStateStats[iStateStatMissing].dblProbabilityVariance = 0; 
			}
		}
		else
		{
			dmattributestat.ulStates = 0;
		}

		if (iPrediction == in_ulMaxPredictions)
		{
			// There's no sort order for the attributes in our model, so
			// we'll simply stop once we've reached the maximum.
			*out_pulPredictions = in_ulMaxPredictions;
			return S_OK;
		}
	}

	// Because there's no missing data, all the attributes are equivalent in terms of any
	// sort order that the caller can request. Thus, we need not worry about performing a sort.
	*out_pulPredictions = iPrediction;
	return S_OK;
}

/*****************************

	Customization Step 3, Item 4

 *****************************/


STDMETHODIMP ALGORITHM::GetNodeIDsForCase( 
        /* [in] */ IDMContextServices*	in_pContext,
        /* [in] */ DM_NODE_FLAGS		in_Flags,
        /* [in] */ DM_CaseID			in_CaseID,
        /* [in] */ ULONG				in_ulCaseValues,
        /* [in] */ DM_ATTRIBUTE_VALUE*	in_rgValues,
        /* [in] */ ULONG				in_ulMaxNodeIDs,
        /* [out] */ DMStringPtr**		io_prgNodeIDs,
        /* [out] */ ULONG*				out_pulNodeIDs)
{
	// This is the main function used for the "DM Dimension" slicing and "drill
	// through". The idea is that your model implements a mapping from cases into 
	// "node ids". For "DM Dimension" mode, the mapping results, for each case, 
	// in exactly one node id for each output variable. In non-DM-Dimension mode, 
	// there can be multiple node ids for each output variable.
	
	// The input DM_NODE_FLAGS specifies that you are to implement the DM-dimension
	// mapping by setting the DMNF_DIMENSION_CONTENT_NODE bits.

	// The node ids returned by this function are the unique names
	// (GetNodeIDFromUniqueName) used by IDMAlgorithmNavigation. You must check
	// if *io_prgNodeIDs is NULL, and if so, you must allocate the appropriate
	// array.

	// For our model, we'll build the following DM Dimension hierarchy: for each 
	// target attribute, we'll test whether the attribute has a posterior mean > 0. 
	// Depending on the outcome, we'll return a different leaf node. The naming scheme
	// and implementation is contained in "Hierarchies.h"
	//
	// For the "Drill through", we need to be consistent with the decision-tree
	// viewer, which means that we have a single root node (i.e. there's no splits 
	// in our "decision tree") that corresponds to every case. 

	UINT ciAttributeOutput = (UINT) _lrpmodel._viAttributeOutput.size();

	if (in_ulMaxNodeIDs < ciAttributeOutput)
	{
		return E_FAIL;
	}

	*out_pulNodeIDs = ciAttributeOutput;

	CComPtr<IDMStringHandler>	spidmstringhandler		= NULL;
	CComPtr<IDMMemoryAllocator>	spidmmemoryallocator	= NULL;
	
	HRESULT hr = in_pContext->GetStringHandler(&spidmstringhandler);

	RETURN_ON_FAIL(hr);

	if (*io_prgNodeIDs == NULL)
	{
		// We have to allocate this array. Regardless of the type of
		// hierarchy, we're going to have one node ID per output attribute.

		hr = in_pContext->GetVectorAlloc(&spidmmemoryallocator);

		RETURN_ON_FAIL(hr);

		DMStringPtr*	rgdmstringNodeIDs = NULL;


		hr = spidmmemoryallocator->Alloc(ciAttributeOutput * sizeof(DMStringPtr), (byte**) &rgdmstringNodeIDs);
		RETURN_ON_FAIL(hr);
		
		memset( rgdmstringNodeIDs, 0, ciAttributeOutput * sizeof(DMStringPtr) );


		for (ULONG iiAttribute = 0; iiAttribute < ciAttributeOutput; iiAttribute++)
		{
			hr = spidmstringhandler->CreateNewStringHandle(&rgdmstringNodeIDs[iiAttribute]);

			RETURN_ON_FAIL(hr);
		}

		*io_prgNodeIDs = rgdmstringNodeIDs;
	}

	DMStringPtr*	rgdmstringNodeIDs = *io_prgNodeIDs;

	WCHAR szNodeID[200];

	if (in_Flags & DMNF_DIMENSION_CONTENT_NODE)
	{
		// We'll construct the hierarchy as described, based on the 
		// posterior distribution. As in Predict(), we'll first convert the 
		// case into a dense vector.

		VDBL	vdblCaseDense(*this);
		
		vdblCaseDense.resize(_lrpmodel._cAttribute, dblMissing);
		
		for (UINT iAVPair = 0; iAVPair < in_ulCaseValues; iAVPair++)
		{
			DM_ATTRIBUTE_VALUE& dmattributevalue = in_rgValues[iAVPair];
		
			ULONG iAttribute = dmattributevalue.Attribute;

			// Check if the attribute is "unspecified" and if so, ignore.
			
			if (iAttribute == DM_UNSPECIFIED)
			{
				continue;
			}
				
			// Note that the translation of the values MUST match that which
			// is done in the CASEPROCESSOR above in InsertCases(). Because we 
			// only have continuous variables, this is a non-issue for us, but 
			// you need to pay attention to this for categorical variables.

			DBL	 dblValue	= ::DblContinuous(dmattributevalue.Value);

			vdblCaseDense[iAttribute]	= dblValue;
		}

		// Get the posterior for each attribute, and set the appropriate name

		LRPHIERARCHY lrphierarchy(true /* bDMDimension*/, ciAttributeOutput);

		for (ULONG iiAttribute = 0; iiAttribute < ciAttributeOutput; iiAttribute++)
		{
			ULONG iAttribute = _lrpmodel._viAttributeOutput[iiAttribute];

			ULONG	iIDRoot = lrphierarchy.IIDRoot(iiAttribute);

			DBL dblPostMean = 0;
			DBL dblPostSD	= 0;

			hr = _lrpmodel.ExtractPosterior(vdblCaseDense, iAttribute, dblPostMean, dblPostSD);

			RETURN_ON_FAIL(hr);

			ULONG iIDLeaf;

			if (dblPostMean > 0)
			{
				iIDLeaf = lrphierarchy.IIDChild(iIDRoot, 0);
			}
			else
			{
				iIDLeaf = lrphierarchy.IIDChild(iIDRoot, 1);
			}

			hr = lrphierarchy.GetSzFromID(szNodeID, iIDLeaf, 199);

			RETURN_ON_FAIL(hr);

			hr = spidmstringhandler->CopyBufferToHandle(rgdmstringNodeIDs[iAttribute], 
													    szNodeID,
														(UINT) wcslen(szNodeID));

			RETURN_ON_FAIL(hr);
		}

		return S_OK;
	}
	else
	{
		// Drill-through mode : use the root node id for each attribute

		LRPHIERARCHY lrphierarchy(false /* bDMDimension*/, ciAttributeOutput);

		for (ULONG iiAttribute = 0; iiAttribute < ciAttributeOutput; iiAttribute++)
		{
			ULONG	iAttribute	= _lrpmodel._viAttributeOutput[iiAttribute];
			ULONG	iIDRoot		= lrphierarchy.IIDRoot(iiAttribute);

			hr = lrphierarchy.GetSzFromID(szNodeID, iIDRoot, 199);

			RETURN_ON_FAIL(hr);

			hr = spidmstringhandler->CopyBufferToHandle(rgdmstringNodeIDs [iiAttribute], 
													    szNodeID,
														(UINT) wcslen(szNodeID));

			RETURN_ON_FAIL(hr);
		}
	}

	return S_OK;
}

/*****************************

	Customization Step 3, Item 7

 *****************************/

	VARIANT	vatTmp;
	::VariantInit(&varTmp);
	switch( in_iParameter )
	{
	case 0:
		V_VT(&varTmp)	=	VT_R8;
		V_R8(&varTmp)	=	(DOUBLE)_dblMinDepScore;
		break;
	case 1:
		V_VT(&varTmp)	=	VT_BOOL;
		V_R8(&varTmp)	=	(BOOL)_bDisplayCorrelation;
		break;
	default:
		// Parameter index out of range
		return E_INVALIDARG;
	}

	// Smart pointer for a variant handler
	CComPtr<IDMVariantPtrHandler> spidmvarianthandler;
	HRESULT hr = in_pContext->GetVariantHandler(&spidmvarianthandler);
	RETURN_ON_FAIL(hr);
	return spidmvarianthandler->CopyVariantToHandle(io_pParameterValue, &varTmp);


/*****************************

	Customization Step 4

 *****************************/

// Persistence element ids for our algorithm

// Persist items
enum DM_PERSIST_ENUM
{
	DM_PAIRWISELINREG = DMPI_ALGORITHMSPECIFIC_BASE,
	DM_ATTRIBUTE_COUNT,
	DM_CASE_COUNT,
	DM_LRPARAM_VIEW,
	DM_LRPARAM_PRED,
	DM_SAMPLE_SD_VECTOR,
	DM_SAMPLE_MEAN_VECTOR,
	DM_MIN_VECTOR,
	DM_MAX_VECTOR,
	DM_POSTERIOR_SD_VECTOR
};

// Helper functions for loading/saving a matrix of regression parameters

STDMETHODIMP	LoadVvlrparam(DM_PERSIST_ITEM		dmpersistitem, 
							  UINT					cAttribute,
							  VVLRPARAM&			vvlrparam, 
							  IDMPersistenceReader* pidmpersistencereader)
{
	HRESULT hr;
	hr = pidmpersistencereader->OpenScope((DM_PERSIST_ITEM) dmpersistitem);		RETURN_ON_FAIL(hr);
	vvlrparam.resize(cAttribute);
	for (UINT iAttribute = 0; iAttribute < cAttribute; iAttribute++)
	{
		ULONG clrparam;
		hr = pidmpersistencereader->GetULong(&clrparam);							RETURN_ON_FAIL(hr);
		vvlrparam[iAttribute].resize(clrparam);
		for (UINT ilrparam = 0; ilrparam < clrparam; ilrparam++)
		{
			LRPARAM& lrparam = vvlrparam[iAttribute][ilrparam];

			// Save in order: score, slope, intercept, sd, regressor, target
			hr = pidmpersistencereader->GetDouble(&lrparam._dblScore);		RETURN_ON_FAIL(hr);
			hr = pidmpersistencereader->GetDouble(&lrparam._dblSlope);		RETURN_ON_FAIL(hr);
			hr = pidmpersistencereader->GetDouble(&lrparam._dblIntercept);	RETURN_ON_FAIL(hr);
			hr = pidmpersistencereader->GetDouble(&lrparam._dblSD);			RETURN_ON_FAIL(hr);

			hr = pidmpersistencereader->GetULong(&lrparam._iAttributeRegressor);	RETURN_ON_FAIL(hr);
			hr = pidmpersistencereader->GetULong(&lrparam._iAttributeTarget);		RETURN_ON_FAIL(hr);
		}
	}

	hr = pidmpersistencereader->CloseScope();	

	return hr;
}


STDMETHODIMP	SaveVvlrparam(DM_PERSIST_ITEM		dmpersistitem, 
							  VVLRPARAM&			vvlrparam, 
							  IDMPersistenceWriter* pidmpersistencewriter)
{
	HRESULT hr;

	hr = pidmpersistencewriter->OpenScope((DM_PERSIST_ITEM) dmpersistitem);	RETURN_ON_FAIL(hr);
	for (UINT iAttribute = 0; iAttribute < vvlrparam.size(); iAttribute++)
	{
		ULONG clrparam = (ULONG) vvlrparam[iAttribute].size();
		hr = pidmpersistencewriter->SetULong(clrparam);						RETURN_ON_FAIL(hr);
		for (UINT ilrparam = 0; ilrparam < clrparam; ilrparam++)
		{
			LRPARAM& lrparam = vvlrparam[iAttribute][ilrparam];

			// Save in order: score, slope, intercept, sd, regressor, target
			hr = pidmpersistencewriter->SetDouble(lrparam._dblScore);		RETURN_ON_FAIL(hr);
			hr = pidmpersistencewriter->SetDouble(lrparam._dblSlope);		RETURN_ON_FAIL(hr);
			hr = pidmpersistencewriter->SetDouble(lrparam._dblIntercept);	RETURN_ON_FAIL(hr);
			hr = pidmpersistencewriter->SetDouble(lrparam._dblSD);			RETURN_ON_FAIL(hr);

			hr = pidmpersistencewriter->SetULong(lrparam._iAttributeRegressor);	RETURN_ON_FAIL(hr);
			hr = pidmpersistencewriter->SetULong(lrparam._iAttributeTarget);		RETURN_ON_FAIL(hr);
		}
	}

	hr = pidmpersistencewriter->CloseScope();	
	return hr;
}

STDMETHODIMP	LoadVdbl(DM_PERSIST_ITEM		dmpersistitem, 
						 VDBL&					vdbl, 
						 IDMPersistenceReader*	pidmpersistencereader)
{
	HRESULT hr;

	hr = pidmpersistencereader->OpenScope((DM_PERSIST_ITEM) dmpersistitem);	
	RETURN_ON_FAIL(hr);

	ULONG cdbl;
	hr = pidmpersistencereader->GetULong(&cdbl);						
	RETURN_ON_FAIL(hr);

	vdbl.resize(cdbl);
	for (UINT idbl = 0; idbl < cdbl; idbl++)
	{
		hr = pidmpersistencereader->GetDouble(&vdbl[idbl]);						
		RETURN_ON_FAIL(hr);
	}

	hr = pidmpersistencereader->CloseScope();	
	return hr;
}

STDMETHODIMP	SaveVdbl(DM_PERSIST_ITEM		dmpersistitem, 
						 VDBL&					vdbl, 
						 IDMPersistenceWriter*	pidmpersistencewriter)
{
	HRESULT hr;

	hr = pidmpersistencewriter->OpenScope((DM_PERSIST_ITEM) dmpersistitem);	
	RETURN_ON_FAIL(hr);

	hr = pidmpersistencewriter->SetULong((ULONG) vdbl.size());						
	RETURN_ON_FAIL(hr);

	for (UINT idbl = 0; idbl < vdbl.size(); idbl++)
	{
		hr = pidmpersistencewriter->SetDouble(vdbl[idbl]);						
		RETURN_ON_FAIL(hr);
	}

	hr = pidmpersistencewriter->CloseScope();	
	return hr;
}


////
//
// IDMPersist interface
//
////

STDMETHODIMP	ALGORITHM::Load(	
				IDMContextServices*		in_pContext,
				IDMPersistenceReader*	in_pReader	)
{
	// Load your model using the IDMPersistenceReader interface. 
	_lrpmodel.SetAllocator(*this);
	HRESULT hr = in_pReader->OpenScope((DM_PERSIST_ITEM) DM_PAIRWISELINREG);
	RETURN_ON_FAIL(hr);

	// Number of attributes
	hr = in_pReader->OpenScope((DM_PERSIST_ITEM) DM_ATTRIBUTE_COUNT);	RETURN_ON_FAIL(hr);
	hr = in_pReader->GetULong(&_lrpmodel._cAttribute);							RETURN_ON_FAIL(hr);
	hr = in_pReader->CloseScope();										RETURN_ON_FAIL(hr);

	// Number of cases
	hr = in_pReader->OpenScope((DM_PERSIST_ITEM) DM_CASE_COUNT);		RETURN_ON_FAIL(hr);
	hr = in_pReader->GetULong(&_lrpmodel._cCase);						RETURN_ON_FAIL(hr);
	hr = in_pReader->CloseScope();										RETURN_ON_FAIL(hr);

	// Viewing parameters
 	hr = LoadVvlrparam(DM_LRPARAM_VIEW, _lrpmodel._cAttribute, _lrpmodel._vvlrparamView, in_pReader);
	RETURN_ON_FAIL(hr);
	hr = LoadVvlrparam(DM_LRPARAM_PRED, _lrpmodel._cAttribute, _lrpmodel._vvlrparamView, in_pReader);

	// Sample mean/SD
	hr = LoadVdbl(DM_SAMPLE_SD_VECTOR, _lrpmodel._vdblSampleSD, in_pReader);
	RETURN_ON_FAIL(hr);

	hr = LoadVdbl(DM_SAMPLE_MEAN_VECTOR, _lrpmodel._vdblSampleMean, in_pReader);
	RETURN_ON_FAIL(hr);

	// Min/Max
	hr = LoadVdbl(DM_MIN_VECTOR, _lrpmodel._vdblMin, in_pReader);
	RETURN_ON_FAIL(hr);

	hr = LoadVdbl(DM_MAX_VECTOR, _lrpmodel._vdblMax, in_pReader);
	RETURN_ON_FAIL(hr);

	// Posterior SD
	hr = LoadVdbl(DM_POSTERIOR_SD_VECTOR, _lrpmodel._vdblPostSD, in_pReader);
	RETURN_ON_FAIL(hr);

	hr = in_pReader->CloseScope(); // DM_PAIRWISELINREG
	
	return S_OK;
}

STDMETHODIMP	ALGORITHM::Save(	
				IDMContextServices*		in_pContext,
				IDMPersistenceWriter*	in_pWriter	)
{
	// Save your model using the IDMPersistenceWriter interface.
	HRESULT hr = in_pWriter->OpenScope((DM_PERSIST_ITEM) DM_PAIRWISELINREG);
	RETURN_ON_FAIL(hr);

	// Number of attributes
	hr = in_pWriter->OpenScope((DM_PERSIST_ITEM) DM_ATTRIBUTE_COUNT);	RETURN_ON_FAIL(hr);
	hr = in_pWriter->SetULong(_lrpmodel._cAttribute);								RETURN_ON_FAIL(hr);
	hr = in_pWriter->CloseScope();										RETURN_ON_FAIL(hr);

	// Number of cases
	hr = in_pWriter->OpenScope((DM_PERSIST_ITEM) DM_CASE_COUNT);		RETURN_ON_FAIL(hr);
	hr = in_pWriter->SetULong(_lrpmodel._cCase);						RETURN_ON_FAIL(hr);
	hr = in_pWriter->CloseScope();										RETURN_ON_FAIL(hr);

	// Viewing parameters
	hr = SaveVvlrparam(DM_LRPARAM_VIEW, _lrpmodel._vvlrparamView, in_pWriter);
	RETURN_ON_FAIL(hr);
	hr = SaveVvlrparam(DM_LRPARAM_PRED, _lrpmodel._vvlrparamView, in_pWriter);

	// Sample mean/SD
	hr = SaveVdbl(DM_SAMPLE_SD_VECTOR, _lrpmodel._vdblSampleSD, in_pWriter);
	RETURN_ON_FAIL(hr);
	hr = SaveVdbl(DM_SAMPLE_MEAN_VECTOR, _lrpmodel._vdblSampleMean, in_pWriter);
	RETURN_ON_FAIL(hr);

	// Min/Max
	hr = SaveVdbl(DM_MIN_VECTOR, _lrpmodel._vdblMin, in_pWriter);
	RETURN_ON_FAIL(hr);
	hr = SaveVdbl(DM_MAX_VECTOR, _lrpmodel._vdblMax, in_pWriter);
	RETURN_ON_FAIL(hr);

	// Posterior SD
	hr = SaveVdbl(DM_POSTERIOR_SD_VECTOR, _lrpmodel._vdblPostSD, in_pWriter);
	RETURN_ON_FAIL(hr);
	hr = in_pWriter->CloseScope(); // DM_PAIRWISELINREG
	
	return S_OK;
}


/*****************************

	Customization Step 5

 *****************************/


HRESULT		NAVIGATOR::Initialize(bool bDMDimension, ALGORITHM* palgorithm)
{
	_bDMDimension	= bDMDimension;
	_palgorithm		= palgorithm;

	// Populate _viAttributeOutput with the attribute indices
	// for all of the output variables, using the attribute-set
	// interface of _palgorithm.

	ULONG	cAttribute;
	HRESULT	hr = palgorithm->_spidmattributeset->GetAttributeCount(&cAttribute);
	RETURN_ON_FAIL(hr);
	_viAttributeOutput.resize(0);
	
	for (ULONG iAttribute = 0; iAttribute < cAttribute; iAttribute++)
	{
		DM_ATTRIBUTE_FLAGS dmattributeflags;
		hr = palgorithm->_spidmattributeset->GetAttributeFlags(iAttribute, &dmattributeflags);
		RETURN_ON_FAIL(hr);

		if (dmattributeflags & DMAF_OUTPUT)
		{
			_viAttributeOutput.push_back(iAttribute);
		}
	}

	_lrphierarchy._ciAttributeOutput	= (UINT) _viAttributeOutput.size();
	_lrphierarchy._bDMDimension			= _bDMDimension;

	// Initialize state
	_iIDNode = _lrphierarchy.IIDRootFirst();
	return S_OK;
}


/*****************************

	Customization Step 5, Item 14

 *****************************/

STDMETHODIMP NAVIGATOR::GetNodeProperty(
	/* [in] */ IDMContextServices*	in_pContext,
	/* [in] */ DM_NODE_PROP			in_PropID,
	/* [in] */ DMVariantPtr			io_pValue)
{
	// Set *io_pValue to the appropriate value, depending on the property
	// desired by the caller. If the current node does not support the given
	// property, the function should return S_FALSE.
	//
	// Following are the allowed "simple" properties, the types expected for the DMVariantPtr, 
	// and a description. The "non simple" properties (DMNP_ATTRIBUTES and DMNP_DISTRIBUTION)
	// are returned using 

	// DMNP_TYPE (ULONG)		: the "type" of the current node. The set of types currently recognized
	//							  by the server are defined (as const ULONG) in "oledbdm.h" and includes:
	//
	//									DM_NODE_TYPE_MODEL        
	//									DM_NODE_TYPE_REGRESSION_TREE_ROOT         
	//									DM_NODE_TYPE_TREE_INTERIOR    
	//									DM_NODE_TYPE_DISTRIBUTION 
	//									DM_NODE_TYPE_CLUSTER      
	//									DM_NODE_TYPE_UNKNOWN      
	//
	//						  If you implement your own browser, you may define your own types
	//
	// DMNP_CONDITION_XML (BSTR) : the "condition" corresponding to the current node, in XML
	//
	// DMNP_RULE_XML (BSTR)	: the "rule" corresponding to the current node, in XML
	//
	// DMNP_PROBABILITY	(double) : the probability corresponding to the current node
	//
	// DMNP_MARGINAL_PROBABILITY (double) : the marginal probability corresponding to the current node
	//
	// DMNP_CAPTION	(BSTR) : the caption of the current node
	//
	// DMNP_SUPPORT	(double) : the support (number of cases) corresponding to the current node
	//
	// DMNP_MODEL_COLUMN_NAME (BSTR) : the "column name" corresponding to the current node
	//
	// DMNP_DESCRIPTION	(BSTR) : longer verion of the caption
	//
	// DMNP_SCORE (double) : the score of the node
	//
	// DMNP_SHORT_CAPTION (BSTR) : shorter version of the caption
	//
	//
	// For the string outputs, you need to allocate using the
	// appropriate methods of in_pContext.

	CComPtr<IDMVariantPtrHandler> spidmvariantptrhandler;
	HRESULT hr	= in_pContext->GetVariantHandler(&spidmvariantptrhandler);
	RETURN_ON_FAIL(hr);
	VARIANT variant;
	::VariantInit(&variant);

	switch (in_PropID)
	{
		case DMNP_TYPE:
		{
			// Cases handled by LRPHIERARCHY
			variant.lVal	= _lrphierarchy.UlType(_iIDNode);
			variant.vt		= VT_I4;

			break;
		}
		case DMNP_CONDITION_XML:
		case DMNP_RULE_XML:
		{
			// There's no condition or rule: the roots are leaves.
			// Return the empty string
			// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
			variant.bstrVal = ::SysAllocString(L"");
			variant.vt		= VT_BSTR;

			break;
		}
		case DMNP_PROBABILITY:
		case DMNP_MARGINAL_PROBABILITY: 
		{
			// We have complete data
			variant.dblVal	= 1.0;
			variant.vt		= VT_R8;
			break;
		}
		case DMNP_CAPTION:
		case DMNP_MODEL_COLUMN_NAME:
		case DMNP_SHORT_CAPTION:
		case DMNP_DESCRIPTION:
		{
			// Use the name of the output attribute associated with this node
			ULONG iiAttribute	= _lrphierarchy.IiAttribute(_iIDNode);
			if (iiAttribute == (ULONG) -1)
			{
				return S_FALSE;
			}

			ULONG iAttribute	= _viAttributeOutput[iiAttribute];
			CComPtr<IDMStringHandler> spidmstringhandler;
			hr	= in_pContext->GetStringHandler(&spidmstringhandler);
			RETURN_ON_FAIL(hr);
			DMString* pdmstring;
			hr = spidmstringhandler->CreateNewStringHandle(&pdmstring);
			RETURN_ON_FAIL(hr);
			
			hr = _palgorithm->_spidmattributeset->GetAttributeDisplayName(
				in_pContext,
				iAttribute,
				(in_PropID == DMNP_SHORT_CAPTION) ? TRUE : FALSE,
				pdmstring);
			RETURN_ON_FAIL(hr);

			// Get the string into a buffer
			UINT cch = 100;
			WCHAR szName[101];
			hr = spidmstringhandler->CopyHandleToBuffer(pdmstring, szName, &cch);
			RETURN_ON_FAIL(hr);

			// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
			variant.bstrVal = ::SysAllocString(szName);
			variant.vt		= VT_BSTR;
			hr = spidmstringhandler->DestroyStringHandle(pdmstring);
			RETURN_ON_FAIL(hr);

			break;
		}
		case DMNP_SCORE:
		{
			// Not defined for our model: return zero
			variant.dblVal	= 0.0;
			variant.vt		= VT_R8;

			break;
		}
		case DMNP_SUPPORT:
		{
			// Total number of cases
			variant.dblVal	= _palgorithm->_lrpmodel._cCase;
			variant.vt		= VT_R8;

			break;
		}
		default:
		{
			return S_FALSE;
		}
	}
	
	return spidmvariantptrhandler->CopyVariantToHandle(io_pValue, &variant);
}
	
/*****************************

	Customization Step 5, Item 15

 *****************************/

STDMETHODIMP NAVIGATOR::GetNodeArrayProperty(
	/* [in] */ IDMContextServices*	in_pContext,
	/* [in] */ DM_NODE_PROP			in_PropID,
	/* [out] */ ULONG*				out_pulArraySize,
	/* [out] */ void**				out_prgArrayBuffer)
{
	IDMContextServices* pidmcontextservices = in_pContext;
	DM_NODE_PROP		dmnodeprop			= in_PropID;

	// Same as above, except for "non simple" properties:

	// DMNP_ATTRIBUTES (DMVariantPtr* BSTR or I4)	: a set of attributes corresponding to the current node
	// DMNP_DISTRIBUTION (DM_ATTRIBUTE_STAT*) : the distribution corresponding to the 
	//											current node						
	
	ULONG iiAttribute	= _lrphierarchy.IiAttribute(_iIDNode);

	if (iiAttribute == (ULONG) -1)
	{
		// There's no output variable associated with the current node, which
		// means we're at the root of the browser hierarchy. We have nothing to
		// provide, regardless of the property requested.

		assert(!_bDMDimension);

		return S_FALSE;
	}

	ULONG iAttribute	= _viAttributeOutput[iiAttribute];

	if (in_PropID == DMNP_ATTRIBUTES)
	{
		// Return the name of the (single) relevant output variable, as a variant
		CComPtr<IDMStringHandler> spidmstringhandler;
		HRESULT hr	= pidmcontextservices->GetStringHandler(&spidmstringhandler);
		RETURN_ON_FAIL(hr);

		DMStringPtr pdmstring;
		hr = spidmstringhandler->CreateNewStringHandle(&pdmstring);
		RETURN_ON_FAIL(hr);

		hr = _palgorithm->_spidmattributeset->GetAttributeDisplayName(
			pidmcontextservices,
			iAttribute,
			FALSE,
			pdmstring);
		RETURN_ON_FAIL(hr);

		// Get the string into a buffer... assume that 100 characters is
		// sufficient

		UINT cch = 100;
		WCHAR szName[101];

		hr = spidmstringhandler->CopyHandleToBuffer(pdmstring, szName, &cch);
		RETURN_ON_FAIL(hr);

		// Create a vector of "DMVariantPtr" of length 1 
		// (because we have a single relevant attribute for this algorithm)
		// then fill its entries with 
		// IDMVariantPtrHandler::CreateNewVariantHandle()
		//

		CComPtr<IDMMemoryAllocator>	spidmmemoryallocator		= NULL;

		hr = in_pContext->GetVectorAlloc(&spidmmemoryallocator);
		RETURN_ON_FAIL(hr);
		
		ULONG	cAttributes = 1;
		
		DMVariantPtr*	rgVariantPtrs = NULL;
		hr = spidmmemoryallocator->Alloc(cAttributes * sizeof(DMVariantPtr),
										 (byte**) &rgVariantPtrs);
		RETURN_ON_FAIL(hr);
		memset( rgVariantPtrs, 0, cAttributes*sizeof(DMVariantPtr) );
		
		
		

		CComPtr<IDMVariantPtrHandler> spidmvarianthandler;
		hr	= pidmcontextservices->GetVariantHandler(&spidmvarianthandler);
		RETURN_ON_FAIL(hr);

		
		
		// Fill the vector entries with a new variant handle
		
		for( ULONG 	nIndex = 0; nIndex < cAttributes; nIndex ++ )
		{
			hr = spidmvarianthandler->CreateNewVariantHandle(&rgVariantPtrs[nIndex]);
			RETURN_ON_FAIL(hr);
		}


		// Just fill the first entry of the vector for this implementation
		VARIANT variant;
		::VariantInit(&variant);

		// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
		variant.bstrVal = ::SysAllocString(szName);
		variant.vt		= VT_BSTR;

		hr = spidmvarianthandler->CopyVariantToHandle(rgVariantPtrs[0], &variant);
		RETURN_ON_FAIL(hr);

		hr = spidmstringhandler->DestroyStringHandle(pdmstring);
		RETURN_ON_FAIL(hr);

		*out_prgArrayBuffer = (void*) rgVariantPtrs;
		*out_pulArraySize	= cAttributes;
	}
	else if (in_PropID == DMNP_DISTRIBUTION)
	{ 
		// We specify a separate DM_ATTRIBUTE_STAT for 
		//
		//	(1) p(missing), the sample mean and the sample variance of the target
		//	(2) Each regressor coefficient or, if the _bDisplayCorrelation flag of
		//		the algorithm is set, each correlation coefficient.
		//  (3) The offset and the conditional variance. Because we are combining
		//		multiple pair-wise regression into the display, there is no single
		//		offset or conditional variance. For simplicity, we'll just use the
		//		sample mean of the target for the offset and the sample variance
		//		of the target for the conditional variance.
		//
		// The order of these array elements is important. Most of the fields need
		// not be specified; we'll initialize all fields to contain zero, and then
		// fill in the needed elements.
		//

 		LRPMODEL&	lrpmodel	= _palgorithm->_lrpmodel;
 		UINT		cRegressor	= (UINT) lrpmodel._vvlrparamView[iAttribute].size();

		UINT cdmattributestat = 2 + cRegressor;

		// Allocate and initialize the output array

		DM_ATTRIBUTE_STAT*	rgdmattributestat = NULL;
        
		CComPtr<IDMMemoryAllocator>     spidmmemoryallocator;
        
		HRESULT hr = pidmcontextservices->GetVectorAlloc(&spidmmemoryallocator);

		RETURN_ON_FAIL(hr);
			
		hr = spidmmemoryallocator->Alloc(cdmattributestat * sizeof(DM_ATTRIBUTE_STAT),
										 (byte**) &rgdmattributestat);
		
		RETURN_ON_FAIL(hr);
	
		// Zero out memory
		memset(rgdmattributestat, 0, sizeof(DM_ATTRIBUTE_STAT) * cdmattributestat);

		// leave NULL all of the DMStringPtr members - they don't need to be populated

		// (1) p(missing), sample mean and sample variance of the target: 
		// We need to populate Attribute with the target attribute index. 
		// Then, as in a call to
		// IDMAlgorithm::Predict(), we create two states in rgStateStats: one 
		// corresponding to missing, and the other corresponding to present. The
		// present state contains, as its value, the sample mean of the target. The 
		// present state also contains the sample variance in dblVariance. Both
		// states need dblSupport and dblProbability to be filled in.... no other 
		// fields need to be set.

		ULONG	cCase	= lrpmodel._cCase;

		rgdmattributestat[0].Attribute	= iAttribute;
		rgdmattributestat[0].ulStates	= 2;

		hr = spidmmemoryallocator->Alloc(2 * sizeof(DM_STATE_STAT),
										 (byte**) &rgdmattributestat[0].rgStateStats);
		
		RETURN_ON_FAIL(hr);

		// Zero out state memory

		memset(rgdmattributestat[0].rgStateStats, 0, 2 * sizeof(DM_STATE_STAT));

		// Missing state

		rgdmattributestat[0].rgStateStats[0].Value = 
			::DmstatevalueContinuous(dblMissing);
		
		rgdmattributestat[0].rgStateStats[0].dblSupport		= 0.0;
		rgdmattributestat[0].rgStateStats[0].dblProbability	= 0.0;
		
		// Non-missing state

		rgdmattributestat[0].rgStateStats[1].Value = 
			::DmstatevalueContinuous(lrpmodel._vdblSampleMean[iAttribute]);
	
		DBL dblSD = lrpmodel._vdblSampleSD[iAttribute];

		rgdmattributestat[0].rgStateStats[1].dblVariance	= dblSD * dblSD;
		rgdmattributestat[0].rgStateStats[1].dblSupport		= cCase;
		rgdmattributestat[0].rgStateStats[1].dblProbability	= 1.0;
		
		// (2) Regression coefficients: we set the iAttribute field to
		// correspond to the regressor, and we create a single state that
		// specifies the value of the coefficient and the sample variance
		// of the regressor. If the algorithm's _bDisplayCorrelation is true,
		// we replace the linear coefficients by the correlation: the correlation
		// is simply the linear coefficient times the ratio of the regressor SD
		// and the target SD.

		for (UINT iRegressor = 0; iRegressor < cRegressor; iRegressor++)
		{
			DM_ATTRIBUTE_STAT&	dmattributestat = rgdmattributestat[1 + iRegressor];
			
			LRPARAM& lrparam = lrpmodel._vvlrparamView[iAttribute][iRegressor];

			dmattributestat.Attribute	= lrparam._iAttributeRegressor;
			dmattributestat.ulStates	= 1;
		
			hr = spidmmemoryallocator->Alloc(sizeof(DM_STATE_STAT),
											 (byte**) &dmattributestat.rgStateStats);
		
			RETURN_ON_FAIL(hr);

			memset(dmattributestat.rgStateStats, 0, sizeof(DM_STATE_STAT));

			// Set the coefficient and sample variance

			if (_palgorithm->_bDisplayCorrelation)
			{
				DBL dblCorrelation = 
					lrparam._dblSlope 
					* lrpmodel._vdblSampleSD[lrparam._iAttributeRegressor]
					/ lrpmodel._vdblSampleSD[iAttribute];

				dmattributestat.rgStateStats[0].Value = 
					::DmstatevalueContinuous(dblCorrelation);
			}
			else
			{
				dmattributestat.rgStateStats[0].Value = 
					::DmstatevalueContinuous(lrparam._dblSlope);
			}
	
			DBL dblSD = lrpmodel._vdblSampleSD[lrparam._iAttributeRegressor];

			dmattributestat.rgStateStats[0].dblVariance = dblSD * dblSD;
		}
	
		// (3) the offset and the conditional variance: we use DM_UNSPECIFIED
		// for the iAttribute, and then create a single state whose value
		// is zero and whose dblVariance is the sample variance.
		
		DM_ATTRIBUTE_STAT&	dmattributestat = rgdmattributestat[1 + cRegressor];
			 
		dmattributestat.Attribute	= DM_UNSPECIFIED;
		dmattributestat.ulStates	= 1;
		
		hr = spidmmemoryallocator->Alloc(sizeof(DM_STATE_STAT),
									     (byte**) &dmattributestat.rgStateStats);
		
		RETURN_ON_FAIL(hr);

		memset(dmattributestat.rgStateStats, 0, sizeof(DM_STATE_STAT));

		// Set the coefficient and sample variance

		dmattributestat.rgStateStats[0].Value = ::DmstatevalueContinuous(0.0);
		dmattributestat.rgStateStats[0].dblVariance = dblSD * dblSD;
		
        *out_pulArraySize	= cdmattributestat;
		*out_prgArrayBuffer = rgdmattributestat;
	}

	return S_OK;
}

